﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Security;
using System.Text;
using hyjx.comm.Constants;
using hyjx.comm.Exceptions;
using hyjx.comm.Interfaces;
using hyjx.comm.Objects;
using hyjx.comm.Types;
using S7.Net;
using S7.Net.Types;

namespace hyjx.comm
{
    /// <summary>
    /// 三菱 MELSEC（MC） 协议
    /// </summary>
    public class MelsecProtocol
    {
        ushort _serialNumber = 0;
        /// <summary>
        /// 创建一个新的三菱 MELSEC（MC） 协议
        /// </summary>
        public MelsecProtocol()
        {

        }

        byte[] GetIncreaseSerialNumber()
        {
            _serialNumber++;

            if (_serialNumber >= ushort.MaxValue)
                _serialNumber = 0;

            return BitConverter.GetBytes(_serialNumber);
        }

        /*
         * 50 00 00 FF FF 03 00 0C 00 08 00 01 04 00 00 00 00 00 A8 02 00
         * D0 00 00 FF FF 03 00 06 00 00 00 00 00 00 00 
         * ---------------------------------------------------
         * 54 00 01 00 00 00 00 FF FF 03 00 0C 00 08 00 01 04 00 00 00 00 00 A8 02 00
         * D4 00 01 00 00 00 00 FF FF 03 00 06 00 00 00 00 00 00 00
         * 
         * 54 00 01 00 00 00 00 FF FF 03 00 0C 00 08 00 01 04 00 00 0D 00 00 A8 02 00
         * D4 00 01 00 00 00 00 FF FF 03 00 06 00 00 00 70 41 00 00
         * bytes[13,14] == 0x00 0x00，表示正常回复，否则两字节放的是出错代码
         * 
         * 54000100000000FFFF03000C00040001040100F90100900100
         * D4 00 01 00 00 00 00 FF FF 03 00 03 00 00 00 10
        5400（固定）
        0100 序号
        0000（固定）
        00FF400001 访问路径
            00 网络编号（固定）
            FF 可编程控制器编号（固定）
            FF03 请求目标模块 IO 编号（固定）
            00 请求目标模块站号（固定）
        0C00 请求数据长度
        0800 监视定时器（2秒）
        0104 指令
        0000 子指令
        000000 软元件编号
        A8 软元件代码
        0200 软元件点数
        -----------------------------------------------------
        D4 00（固定）
        06 00 数据长度
        00 00 结束代码
        00 00 00 00（70 41 00 00）
         */

        /// <summary>
        /// 生成 4E 帧指令
        /// </summary>
        /// <param name="softComponentId">软元件编号</param>
        /// <param name="softComponentPoint">软元件点数</param>
        /// <param name="code">软元件代码</param>
        /// <param name="timeoutSeconds">超时秒数</param>
        /// <param name="sequence">序列号</param>
        /// <param name="writeDatas">写数据</param>
        /// <returns></returns>
        public List<byte> Build4ECommand(int softComponentId, ushort softComponentPoint, SoftComponentCodeTypes code, ushort sequence, ushort timeoutSeconds, params byte[] writeDatas)
        {
            // 采用 4E 帧
            List<byte> bytes = new List<byte>()
            {
                0x54, 0x00, // 副帧头
            };
            bytes.AddRange(BitConverter.GetBytes(sequence)); // 序号
            bytes.AddRange(new byte[] { 0, 0 }); // 序号空余（固定）
            bytes.Add(0); // 网络编号（固定）
            bytes.Add(0xff); // PLC 编号（固定）
            bytes.AddRange(new byte[] { 0xff, 0x03 }); // 目标模块 IO 编号（固定）
            bytes.Add(0); // 目标模块站号（固定）
            ushort length = 2 + 2 + 2 + 3 + 1 + 2;
            bytes.AddRange(BitConverter.GetBytes(length));// 数据长度，2 字节，低位在前
            bytes.AddRange(BitConverter.GetBytes(Convert.ToUInt16(timeoutSeconds * 4))); // 监视定时器，2 字节，低位在前，16*250ms = 4s

            if (writeDatas.Length == 0)
                bytes.AddRange(MelsecConstant.ReadCommandConstant); // 批量读取指令
            else
                bytes.AddRange(MelsecConstant.WriteCommandConstant); // 批量写入指令

            // 子指令
            List<byte> childCommand = new List<byte>();
            switch(code)
            {
                case SoftComponentCodeTypes.SD:
                case SoftComponentCodeTypes.D:
                case SoftComponentCodeTypes.W:
                case SoftComponentCodeTypes.SW:
                    childCommand.Add(0x00); // 按字取
                    break;
                default:
                    childCommand.Add(0x01);// 按位取
                    break;
            }

            childCommand.Add(0x00);
            bytes.AddRange(childCommand);

            byte[] startBytes = BitConverter.GetBytes(softComponentId);//
            bytes.AddRange(new byte[] { startBytes[0], startBytes[1], startBytes[2] }); // 起始软元件，3 字节
            bytes.Add((byte)code); // 软元件代码
            bytes.AddRange(BitConverter.GetBytes(softComponentPoint)); // 软元件点数softComponentPoint

            if (writeDatas != null && writeDatas.Length > 0) // 追加要写入的数据
                bytes.AddRange(writeDatas);

            return bytes;
        }
        /// <summary>
        /// 解析指令
        /// </summary>
        /// <param name="command">指令</param>
        /// <returns>解析指令结果</returns>
        public ParseMcCommandResult ParseCommand(byte[] command)
        {
            /*
             * D4 00 01 00 00 00 00 FF FF 03 00 06 00 00 00 70 41 00 00
             * D4 00 01 00 00 00 00 FF FF 03 00 03 00 00 00 10
             * 
            D400（固定）
            0100 序号
            0000（固定）
            00FF400001 访问路径
            00 网络编号（固定）
            FF 可编程控制器编号（固定）
            FF03 请求目标模块 IO 编号（固定）
            00 请求目标模块站号（固定）
            0600 请求数据长度
            0000 错误代码
            bytes[13,14] == 0x00 0x00，表示正常回复，否则两字节放的是出错代码
             */

            ushort length = (ushort)(BitConverter.ToUInt16(new byte[] { command[11], command[12] }) - 2);
            byte[] datas = new byte[length];
            Array.Copy(command, 15, datas, 0, length);
            ParseMcCommandResult result = new ParseMcCommandResult()
            {
                SerialNumber = BitConverter.ToUInt16(new byte[] { command[2], command[3] }, 0),
                DataBytes = datas,
                ErrorCode = BitConverter.ToUInt16(new byte[] { command[13], command[14] })
            };
            return result;
        }

        /// <summary>
        /// 转换位软元件集
        /// </summary>
        /// <param name="bytes">字节流</param>
        /// <returns>布尔值集</returns>
        public List<bool> ConvertBitSoftComponents(byte[] bytes)
        {
            List<bool> result = new List<bool>();
            foreach (var item in bytes)
            {
                switch (item)
                {
                    case 0:
                        result.Add(false);
                        result.Add(false);
                        break;
                    case 1:
                        result.Add(false);
                        result.Add(true);
                        break;
                    case 16:
                        result.Add(true);
                        result.Add(false);
                        break;
                    case 17:
                        result.Add(true);
                        result.Add(true);
                        break;
                    default:
                        break;
                }
            }
            return result;
        }

        #region[  释放对象  ]

        /// <summary>
        /// 释放对象
        /// </summary>
        public void Dispose()
        {
            Dispose(disposing: true);
            // 释放完成后，调用该方法通知 GC 将该对象从终结器队列中移除。这样该对象的析构函数就不会被执行。
            GC.SuppressFinalize(this);
        }

        private bool disposed = false;


        /// <summary>
        /// 执行具体的释放
        /// </summary>
        /// <param name="disposing">false : 由终结器释放</param>
        protected virtual void Dispose(bool disposing)
        {
            // 如果以执行完释放，不需要做任何动作
            if (this.disposed)
            {
                return;
            }

            if (disposing)
            {
                // 在此释放托管资源
            }

            // 开始释放非托管资源

            // 释放已完成
            disposed = true;
        }

        /// <summary>
        /// 析构函数（终结器）
        /// </summary>
        ~MelsecProtocol()
        {
            // 不要在此处编写清理代码。
            // 应调用 Dispose(disposing: false)
            Dispose(disposing: false);
        }
        #endregion
    }
}
